<?php

class MVC_Exception extends Exception {
	public $_message;
	public $_code;
	public $_class;
	public $_file;
	public $_line;
	public $_trace;
	public $_source;

	public $_type = 'exception';

	public function __construct($message, $code = 0, Exception $previous = null) {
		if(!isset($this->_message)) $this->_message = $message;
		if(!isset($this->_code)) $this->_code = $code != 0 ? $this->readable($code) : NULL;
		if(!isset($this->_class)) $this->_class = get_class($this);
		if(!isset($this->_file)) $this->_file = $this->getFile();
		if(!isset($this->_line)) $this->_line = $this->getLine();
		if(!isset($this->_trace)) $this->_trace = $this->getStack();
		if(!isset($this->_source)) $this->_source = $this->getSource(@file($this->_file), $this->_line, 0);

		parent::__construct($message, $code, $previous);
	}

	private function readable($code) {
		if($this->_type == 'exception')
			return NULL;

		$definition = array (
			E_ERROR => 'Error',
			E_WARNING => 'Warning',
			E_PARSE => 'Parsing Error',
			E_NOTICE => 'Notice',
			E_CORE_ERROR => 'Core Error',
			E_CORE_WARNING => 'Core Warning',
			E_COMPILE_ERROR => 'Compile Error',
			E_COMPILE_WARNING => 'Compile Warning',
			E_USER_ERROR => 'User Error',
			E_USER_WARNING => 'User Warning',
			E_USER_NOTICE => 'User Notice',
			E_STRICT => 'Runtime Notice',
			E_RECOVERABLE_ERROR  => 'Catchable Fatal Error',
			E_DEPRECATED => 'Deprecated',
			E_USER_DEPRECATED => 'User Deprecated'
		);

		return $definition[$code];
	}

	private function getSource($source, $error, $level = 0, $lines = 10) {
		$output = null;
		$found = false;
		$begin = $e = $error - $lines > 0 ? $error - $lines : 1;
		$end = $error + $lines <= count($source) ? $error + $lines : count($source);
		$mark = $level == 0 ? 'error' : 'warn';

		// colorize
		foreach ($source as $idx => &$line) {
			$colorize = null;

			if(preg_match('/\/\*/', $line)) $found = true; // fix comments
			if(preg_match('/<\?(php)?[^[:graph:]]/', $line)) $colorize .= str_replace(array('<code>', '</code>'), '', highlight_string($line, true)); // fix colors
			else {
				if($found) $colorize .= preg_replace(array('/(&lt;\?php&nbsp;)+/', '/\/\//'), '', str_replace(array('<code>', '</code>'), '', highlight_string('<?php //' . $line, true))); // fix comment
				else $colorize .= preg_replace('/(&lt;\?php&nbsp;)+/', '', str_replace(array('<code>', '</code>'), '', highlight_string('<?php ' . $line, true))); // fix colors
			}
			if(preg_match('/\*\//', $line)) $found = false; // end fix comments

			// output the marked line or the normal lines
			if(($idx + 1) === $error) $line = "<tr><td><div class='{$mark}' style='padding-left: 5px;'>" . ($idx + 1) . ".</div></td><td><div class='{$mark}' style='padding-left: 5px;'>{$colorize}</div></td></tr>";
			else $line = "<tr><td style='padding-left: 5px;'>" . ($idx + 1) . ".</td><td style='padding-left: 5px;'>{$colorize}</td></tr>";
		}

		// only get a certain number of lines to show
		for($i = $begin - 1; $i < $end; $i++) {
			$output .= $source[$i];
		}

		return "<table cellpadding='0' cellspacing='0' style='width: 100%; line-height: 15px; font-size: 0.95em; font-family:\"lucida console\", Consolas, \"andale mono\", \"monotype.com\", monospace;'><col width='1%' /><col width='99%' />{$output}</table>";
	}

	private function getStack() {
		$output = null;
		$trace = $this->getTrace();

		foreach($trace as $idx => $line) {
			if(!isset($line['file']) && !isset($line['line'])) unset($trace[$idx]);
			elseif($line['file'] == $this->_file && $line['line'] == $this->_line) unset($trace[$idx]);
		}

		$i = count($trace);
		foreach($trace as $line) {
			$line['class'] = isset($line['class']) ? $line['class'] : NULL;
			$line['type'] = isset($line['type']) ? $line['type'] : NULL;

			$output .= "<div id='in_{$i}' class='title' title='expand stack #{$i}'><span class='number'>#{$i}.</span><span class='func'>{$line['class']}{$line['type']}{$line['function']}()</span><small>{$line['file']} (line: {$line['line']})</small></div><br />";
			$output .= "<div id='out_{$i}' style='display: none;'><div class='indent'>" . $this->getSource(@file($line['file']), $line['line'], 1, 5) . "</div></div>";

			$i--;
		}

		return strlen($output) == 0 ? "<small>Nothing here pal :(</small>" : $output;
	}
}